简介
Thrift是一个软件框架,用来进行可扩展且跨语言的服务的开发。它结合了功能强大的软件堆栈和代码生成引擎,以构建在 C++, Java, Go,Python, PHP, Ruby, Erlang, Perl, Haskell, C#, Cocoa, JavaScript, Node.js, Smalltalk, and OCaml 这些编程语言间无缝结合的、高效的服务。Thrift最初由facebook开发,07年四月开放源码,08年5月进入apache孵化器。thrift允许你定义一个简单的定义文件中的数据类型和服务接口。以作为输入文件,编译器生成代码用来方便地生成RPC客户端和服务器通信的无缝跨编程语言。
本文主要介绍 Thrift 开发流程,并给出 Java 开发 Thrfit 应用的实例。
本文的开发环境为: macOS Sierra 10.12.6
Thrift开发概述
Thrift用接口描述语言(Interface description language,IDL)来描述不同编程语言之间的接口。Thrift开发环境包含两个部分:Thrift编译器和语言相关的库。Thrift编译器用来根据IDL生成语言相关的接口代码框架,我们可以使用这个框架很方便的实现客户端和服务器的代码。语言相关的库则封装了不同编程语言之间通信的内部实现,让我们解放双手着重处理业务逻辑。根据编程语言的不同,构建相关库的方法也不同。
开发前的准备
- 在Thrift官方下载页面下载Thrift编译器,如果希望从源代码编译Thrift编译器,则跳过这一步。官网只提供了windows下的编译器,thrift-0.10.0.exe,使用非windows跳过此步骤,mac系统请参考Mac下安装Thrift。
- 在Thrift官方下载页面下载Thrift源代码。源代码中包含了编译器的代码和语言相关的库的代码。本例下载的源代码为:thrift-0.10.0.tar.gz 。下载后解压到合适的目录。如果希望从源代码编译Thrift编译器,请参阅Thrift源代码根目录下的README.md。官网教程 Building from source
- 构建语言相关的库(如果需要的话)。
Mac下安装Thrift
在mac下安装软件跟Linux安装比较类似,在安装Thrift之前需要先安装依赖。
安装BOOST
下载:http://www.boost.org/ (boost_1_57_0.tar.gz)
拷贝到/usr/local目录下并解压:
tar -zvxf boost_1_57_0.tar.gz
切换目录:
cd boost_1_57_0
执行命令:
./bootstrap.sh —prefix=PATH_TO_BOOST
sudo ./b2 threading=multi address-model=64 variant=release stage install
./bootstrap.sh该命令用于生成bjam可执行文件,这个东西就是用来编译boost库
安装 libevent
下载:http://libevent.org/ (libevent-2.0.21-stable.tar.gz)
拷贝到/usr/local目录下并解压:
tar -zvxf libevent-2.0.21-stable.tar.gz
切换目录:
cd libevent-2.0.21-stable
执行命令:
./configure —-prefix=/usr/local
make
sudo make install
执行make时报如下错误,需要安装openssl; 这是由于mac默认安装了openssl但是没有安装对应的include头文件和lib库,故这里使用homebrew(参考https://brew.sh/index_zh-cn.html)来安装最新的openssl
fatal error: 'openssl/bio.h' file not found
使用homebrew 安装
brew install openssl
安装后,连接到libevent目录下
ln -s /usr/local/Cellar/openssl/1.0.2o_2/include/openssl /usr/local/libevent-2.1.8-stable/include
成功后再执行 make和sudo make install命令就不报错了
安装 Apache Thrift
下载:http://thrift.apache.org/ (thrift-0.8.0.tar.gz)
解压:
tar -zvxf thrift-0.8.0.tar.gz
切换目录:
cd thrift-0.8.0.tar.gz
编译命令:
./configure --prefix=/usr/local/ --with-boost=/usr/local --with-libevent=/usr/local
安装命令:
sudo make install
可以查看安装是否成功和版本
thrift -version
注意版本选择:选择Thrift 0.8.0版本,选择高的版本会报bison版本低的错误,在安装0.8.0时也报了一些错误,不过不影响正常使用
最新版0.11.0 在mac中如果用上面步骤安装不成功,可以使用如下方式安装
brew install thrift
开发流程
- 使用IDL定义服务器和客户端之间的接口。利用Thrift编译器编译该IDL文件,生成语言相关的代码框架。
- 利用已经生成的代码框架,实现客户端和服务器的业务逻辑。
构建语言相关的库
语言相关的库代码在Thrift源代码的lib下,每种语言一个目录,比如C++就在cpp目录下。在每种语言的目录下,都有一个README.md用来描述该语言库的构建和使用方法。语言相关的库根据编程语言的不同,构建的方式也不同。如果是编译型语言的话,则需要编译。本篇只提供java案例
构建Java库
本例构建环境:
java version "1.8.0_131"
Apache Ant(TM) version 1.10.5
在编译之前请确保正确安装和配置了 JDK 和 Ant。
Java库源代码目录为:[Thrift源代码目录]\lib\java。Java库需要使用Ant 来编译。管理员方式打开命令行,切换到Java库元源代码目录,执行:
ant
编译后会在 build 子目录下找到 libthrift-0.10.0.jar,这就是我们需要的开发包。在 build/lib 下是 libthrift-0.10.0.jar 的依赖包。
使用IDL定义接口
Thrift 中 IDL 文件以 .thrift 为后缀。该文件用来描述服务器与客户端之间的接口。在编写 thrift 文件之前,还需要了解一下用以描述接口的语法。
注释
Thrift 支持三种注释:
脚本注释,用 # 表示,例如:
#这是一个注释
块注释,用 / 和 / 表示。
/*
- 这是一个注释
*/
单行注释,用 // 表示。
// 这是一个注释
基本类型
类型 | 描述 |
---|---|
bool | 布尔类型,1byte |
i8 | 有符号整形,8bits,即byte |
i16 | 有符号整型,16bits |
i32 | 有符号整型,32bits |
i64 | 有符号整型,64bits |
double | 浮点型,64bits |
string | 字符串 |
binary | Blob,byte数组 |
map<t1,t2> | map |
list |
有序列表 |
set |
无重复元素的容器 |
常量
常量用const表示:
const i32 MATHPATH = 256
枚举
用 enum 定义枚举。枚举类型是32位的整数。值是可选的,从1开始。
enum Operation {
ADD = 1,
SUBTRACT = 2,
MULTIPLY = 3,
DIVIDE = 4
}
结构体
即java中的bean, 结构体用 struct 表示,是基本的复合数据类型,由若干字段组成。每个字段由整型ID,类型,名称和可选的默认值组成。字段可以声明为 optional,表示如果没有被设置,则不进行序列化。
struct Work {
1: i32 num1 = 0,
2: i32 num2,
3: Operation op,
4: optional string comment,
}
结构体也可以定义为异常:
exception InvalidOperation {
1: i32 whatOp,
2: string why
}
服务
服务类似 class, 用 service 关键字定义。服务可以用 extends 关键字继承其他服务。service 由一系列方法组成。方法由返回值,参数列表和一个可选的异常列表组成。参数列表和异常列表的语法和结构体的语法一样。
service Calculator extends shared.SharedService {
void ping(),
i32 add(1:i32 num1, 2:i32 num2),
i32 calculate(1:i32 logid, 2:Work w) throws (1:InvalidOperation ouch),
/**
* oneway 表示客户端发送请求后不需要响应。onway 方法返回值必须是void
*/
oneway void zip()
}
include 指令
用 include 指令包含其他 thrift 文件:
include "another.thrift"
namespace 指令
namespace 指令指定语言相关的名称空间:
namespace cpp nscpp # 指定C++的名称空间为 nscpp
namespace java nsjava # 指定Java的包为 nsjava
typedef 指令
typedef 指令用来指定类型别名,和 C 一样。
typedef i32 MyInteger
编译IDL文件
Thrift 编译器的用法如下:
thrift [options] file
可以通过 –help 选项来了解具体用法:
thrift --help
通常用以下命令将IDL编译成语言相关的接口代码,-r(recurse的首字母)表示递归生成被包含的文件,–gen后面接生成的语言。
thrift-0.10.0.exe -r --gen java ICalc.thrift
实例详解
下面分别给出 JAVA开发 Thrift 应用的实例。需要注意的是某种语言开发的服务器和任何语言开发的客户端都可以实现互联,这正是 Thrift 的特性之一。
Java开发实例
本例开发环境:
java version "1.8.0_131"
Apache Ant(TM) version 1.10.1
在开发之前应确保 Java 的 Thrift 包已经编译好,如果还没有编译好,请参见构建Java库。
首先建立如下所示的目录结构。其中 com/cynhard/thrift/test 是包的路径,可以根据自己的域名进行调整。iface, server, client三个目录分别用来存放接口,服务器,客户端的代码。build.xml是Ant构建文件。
[somewhere]
|-calc/
|-com/
| |-cynhard/
| |-thrift/
| |-test/
| |-iface/ -- 接口文件目录
| | |-ICalc.thrift -- 接口文件
| |-server/ -- 服务器目录
| | |-Server.java -- 服务器实现文件
| |-client/ -- 客户端目录
| |-Client.java -- 客户端实现文件
|-build.xml -- ant 文件
定义接口
打开 ICalc.thrift 文件,编写接口代码如下。注意包名应该根据自己的实际路径调整。
ICalc.thrift
namespace java com.cynhard.thrift.test.iface
service ICalc {
i32 add(1:i32 num1, 2:i32 num2),
}
实现服务器
打开 Server.java,编写服务器代码如下。注意包名应该根据自己的实际路径调整。
Server.java
package com.cynhard.thrift.test.server;
import org.apache.thrift.TException;
import org.apache.thrift.server.TServer;
import org.apache.thrift.server.TServer.Args;
import org.apache.thrift.server.TSimpleServer;
import org.apache.thrift.transport.TServerSocket;
import org.apache.thrift.transport.TServerTransport;
import com.cynhard.thrift.test.iface.ICalc;
public class Server {
static class CalcHandler implements ICalc.Iface {
@Override
public int add(int num1, int num2) throws TException {
return num1 + num2;
}
}
public static void main(String[] args) {
try {
CalcHandler handler = new CalcHandler();
ICalc.Processor processor = new ICalc.Processor(handler);
TServerTransport serverTransport = new TServerSocket(9090);
TServer server = new TSimpleServer(new Args(serverTransport).processor(processor));
System.out.println("Starting the server...");
server.serve();
} catch (Exception e) {
e.printStackTrace();
}
}
}
实现客户端
打开 Client.java,编写客户端代码如下。注意包名应该根据自己的实际路径调整。
Client.java
package com.cynhard.thrift.test.client;
import org.apache.thrift.TException;
import org.apache.thrift.transport.TTransport;
import org.apache.thrift.transport.TSocket;
import org.apache.thrift.protocol.TBinaryProtocol;
import org.apache.thrift.protocol.TProtocol;
import com.cynhard.thrift.test.iface.ICalc;
public class Client {
public static void main(String[] args) {
try {
TTransport transport = new TSocket("localhost", 9090);
transport.open();
TProtocol protocol = new TBinaryProtocol(transport);
ICalc.Client client = new ICalc.Client(protocol);
System.out.println(client.add(1, 2));
transport.close();
} catch (TException e) {
e.printStackTrace();
}
}
}
构建工程
打开 build.xml,编写构建配置如下。注意路径属性需要根据实际路径进行修改。
build.xml
<?xml version="1.0"?>
<project name="calc" basedir="." default="build">
<property name="thrift.lib.dir" value="/Users/zhouguangsheng/project/study/Thrift/thrift-0.11.0/lib/java/build"/>
<property name="thrift.compiler.dir" value="/Users/zhouguangsheng/project/study/Thrift/thrift-0.11.0/lib/java/build"/>
<property name="interface.dir" value="${basedir}\com\cynhard\thrift\test\iface"/>
<path id="master-classpath">
<fileset dir="${thrift.lib.dir}">
<include name="**\*.jar"/>
</fileset>
<pathelement path="."/>
</path>
<target name="build" description="Compile Calc">
<!-- 如果单独编译thrift文件,注释下面exec这段 -->
<exec executable="${thrift.compiler.dir}/libthrift-0.11.0.jar">
<arg line="--gen java -out ${basedir} ${interface.dir}\ICalc.thrift"/>
</exec>
<javac>
<src path="."/>
<classpath refid="master-classpath"/>
</javac>
</target>
<target name="run_server" description="Run server">
<java fork="true" failonerror="yes" classname="com.zgs.thrift.test.server.Server">
<classpath refid="master-classpath"/>
</java>
</target>
<target name="run_client" description="Run client">
<java fork="true" failonerror="yes" classname="com.zgs.thrift.test.client.Client">
<classpath refid="master-classpath"/>
</java>
</target>
</project>
上面的工作完成后,就可以开始构建工程了。以管理员权限打开命令提示符,切换到 项目根目录。执行以下命令编译工程:
ant build
直接编译可能会不成功,thrift文件也可以单独编译,ant build.xml文件中移除,只编译java
thrift -gen java -out /Users/zhouguangsheng/project/study/Thrift/thriftDemo/ /Users/zhouguangsheng/project/study/Thrift/thriftDemo/com/zgs/thrift/test/iface/ICalc.thrift
-out参数为编译的java文件要存放的路径
编译成功后,服务器和客户端相应的class文件就生成在了相应的目录中。下面就可以开始测试了。
测试
执行以下命令运行服务器。
ant run_server
输出结果如下,可以看到输出了 Starting the server…,表明服务器已经启动。
Buildfile: G:\projects\java\calc\build.xml
run_server:
[java] Starting the server...
因为服务器在一个单独的窗口运行,为了测试客户端,需要打开另一个命令提示符,同样切换到 calc 目录。执行以下命令运行客户端:
ant run_client
输出结果如下,可以看到返回了正确的结果:3 。
Buildfile: G:\projects\java\calc\build.xml
run_client:
[java] Received 1
[java] 3
BUILD SUCCESSFUL
Total time: 0 seconds